#include <math.h>
//#include <GL/glut.h>
#include <list>
#include <iostream>
//#include <GL/glui.h>
#include <GL/freeglut.h>
#include <sstream>
#include <string>

int screenWidth = 640;
int screenHeight = 480;

int mouseX = 0;
int mouseY = 0;
int mouseOldX = 0;
int mouseOldY = 0;

class Point {
    public:
        int x, y;
        Point(int x1, int y1)
        {
            x = x1;
            y = y1;
        }
};

typedef std::list<Point> Polygon;
typedef std::list<Polygon> PolygonList;


// List of all our polygons
PolygonList polygonList;


void myInit(void)
{
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glColor3f(0.0f, 0.0f, 0.0f);
    glPointSize(1.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, (GLdouble )screenWidth, 0.0, (GLdouble)screenHeight);
}


void drawSquare(int x, int y)
{
    glBegin(GL_QUADS);
    glVertex3f(x - 3, y + 3, 0.0f);
    glVertex3f(x + 3, y + 3, 0.0f);
    glVertex3f(x + 3, y - 3, 0.0f);
    glVertex3f(x - 3, y - 3, 0.0f);
    glEnd();
}


void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    Polygon::iterator iter;
    PolygonList::iterator listIter;


    // Draw all Polygons in List
    for(listIter = polygonList.begin(); listIter != polygonList.end(); listIter++)
    {
        
        Polygon tempPolygon = *listIter;
        glBegin(GL_LINE_STRIP);
        
        // If incomplete polygon, draw it red
        if (listIter == polygonList.begin())
        {
            glColor3f(1.0f, 0.0f, 0.0f);
            glVertex2i(mouseX, mouseY);
        }
        else
        {
            glColor3f(0.0f, 0.0f, 0.0f);
        }

        for(iter = tempPolygon.begin(); iter != tempPolygon.end(); iter++)
        {

            Point currentPoint = *iter;
            glVertex2i(currentPoint.x, currentPoint.y);
        }
        glEnd();
    }

    // Draw Squares
    for(listIter = polygonList.begin(); listIter != polygonList.end(); listIter++)
    {
        if (listIter == polygonList.begin())
        {
            glColor3f(1.0f, 0.0f, 0.0f);
        }
        else
        {
            glColor3f(0.0f, 0.0f, 0.0f);
        }

        Polygon tempPolygon = *listIter;
        for(iter = tempPolygon.begin(); iter != tempPolygon.end(); iter++)
        {

            Point currentPoint = *iter;            
            drawSquare(currentPoint.x, currentPoint.y);
        }
    }

    // Draw Helper Text
    glColor3f(0.0f, 0.0f, 0.0f);
    glRasterPos2i(5, screenHeight-15);
    glutBitmapString(GLUT_BITMAP_HELVETICA_12, (const unsigned char*) "(left click) add point \n(right click + drag) move nearest point \n(c) clear points \n(d) delete nearest point");


    glRasterPos2i(mouseX, mouseY);
    std::stringstream pos;
    pos << "(" <<  mouseX << ", " << mouseY << ")";
    glutBitmapString(GLUT_BITMAP_HELVETICA_12, (const unsigned char*) pos.str().c_str());
    
    glutSwapBuffers();

}

void shiftNearestPoint(int x, int y, int x_shift, int y_shift)
{

    PolygonList::iterator closestPolygon = polygonList.begin();
    Polygon::iterator closestPoint = closestPolygon->begin();

    float closestDistance = sqrt(pow(x - closestPoint->x, 2) + pow(y - closestPoint->y, 2));

    // Search PolygonList
    PolygonList::iterator listIter;
    Polygon::iterator iter;

    for(listIter = polygonList.begin(); listIter != polygonList.end(); listIter++)
    {

        for(iter = listIter->begin(); iter != listIter->end(); iter++)
        {
            const float distance = sqrt(pow(x - iter->x, 2) + pow(y - iter->y, 2));

            if (distance < closestDistance)
            {
                closestPoint = iter;
                closestDistance = distance;
            }
        }

    }

    closestPoint->x += x_shift;
    closestPoint->y += y_shift;

}

void deleteNearestPoint(int x, int y)
{
    y = screenHeight - y;

    PolygonList::iterator closestPolygon = polygonList.begin();
    Polygon::iterator closestPoint = closestPolygon->begin();

    float closestDistance = sqrt(pow(x - closestPoint->x, 2) + pow(y - closestPoint->y, 2));

    // Search PolygonList
    PolygonList::iterator listIter;
    Polygon::iterator iter;

    for(listIter = polygonList.begin(); listIter != polygonList.end(); listIter++)
    {

        for(iter = listIter->begin(); iter != listIter->end(); iter++)
        {
            const float distance = sqrt(pow(x - iter->x, 2) + pow(y - iter->y, 2));

            if (distance < closestDistance)
            {
                closestPolygon = listIter;
                closestPoint = iter;
                closestDistance = distance;
            }
        }

    }

    closestPolygon->erase(closestPoint);

    glutPostRedisplay();

}


void addPoint(int x, int y)
{
    Point currentPoint(x, y);
    Polygon &currentPolygon = polygonList.front();

    if ((x > currentPolygon.back().x - 25 && x < currentPolygon.back().x + 25) &&
            (y > currentPolygon.back().y - 25 && y < currentPolygon.back().y + 25))
    {
        // Snap to first point, auto-completing polygon
        currentPoint = Point(currentPolygon.back().x, currentPolygon.back().y);
        currentPolygon.push_front(currentPoint);

        // New Polygon pushed onto Polygon List    
        Polygon newPolygon;
        polygonList.push_front(newPolygon);

    }
    else
    {
        currentPolygon.push_front(currentPoint);
    }

        std::cout << "Added Point(" << x << ", " << y << ")\n";
}


void debug()
{

    // Search PolygonList
    PolygonList::iterator listIter;
    Polygon::iterator iter;
    int polygonCounter = 0;

    for(listIter = polygonList.begin(); listIter != polygonList.end(); listIter++)
    {
        Polygon &tempPolygon = *listIter;
        std::cout << "Polygon #: ";
        std::cout << polygonCounter << "\n";

        for(iter = tempPolygon.begin(); iter != tempPolygon.end(); iter++)
        {
            std::cout << "Point: (";
            std::cout << iter->x;
            std::cout << ", ";
            std::cout << iter->y;
            std::cout << ")\n";


        }

        polygonCounter++;
    }

    std::cout << "\n";

}

void clearPoints()
{
    polygonList.clear();
    Polygon newPolygon;
    polygonList.push_front(newPolygon);
    glutPostRedisplay();
}

void keyboardHandler(unsigned char thekey, int x, int y)
{
    if (thekey == 'd') deleteNearestPoint(x,y);
    if (thekey == 'c') clearPoints();
    if (thekey == 't') debug();
}


void mouseHandler(int button, int state, int x, int y)
{
    y = screenHeight - y;
    mouseOldY = y;
    mouseOldX = x;

    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        addPoint(x, y);
    }
    

    glutPostRedisplay();
}

void mouseDragHandler(int x, int y)
{
    y = screenHeight - y;

    int y_diff = y - mouseOldY;
    int x_diff = x - mouseOldX;

    shiftNearestPoint(x, y, x_diff, y_diff);

    mouseOldY = y;
    mouseOldX = x;

    glutPostRedisplay();

}

void mousePassiveHandler(int x, int y)
{
    y = screenHeight - y;

    mouseX = x;
    mouseY = y;

    glutPostRedisplay();
}



void reshape(int w, int h)
{
    glViewport (0, 0, (GLsizei) w, (GLsizei) h);
    screenHeight = h;
    screenWidth = w;

    std::cout << "Window Width: " << w  << ", " << h;
}

int main(int argc, char ** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(screenWidth, screenHeight);
    glutInitWindowPosition(100, 150);
    glutCreateWindow("Polygon Draw!");
    glutDisplayFunc(redraw);
    glutMouseFunc(mouseHandler);
    glutKeyboardFunc(keyboardHandler);
    glutPassiveMotionFunc(mousePassiveHandler);
    glutReshapeFunc(reshape);
    glutMotionFunc(mouseDragHandler);


    // Push our first polygon onto the polygonList
    Polygon currentPolygon;
    polygonList.push_front(currentPolygon);


    myInit();
    glutMainLoop();
    return 0;
}

